using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


using ArkanoidX.RacketStates;

namespace ArkanoidX.Objects
{
    /// <summary>
    /// This is a _game component that implements IUpdateable.
    /// 
    /// 'Racket' in arkanoid is also known as 'Paddle' in the original game
    /// </summary>
    public partial class Racket : StatefulComponent, IPhysicalObject
    {
        public abstract class State : StatefulComponent.State<Racket>
        {
            public enum InternalState
            {
                Inactive,
                Starting,   /* undrawable state, draw last-state instead */
                FadeIn,
                FadingIn,
                Active,
            }


            public Rectangle _reachableArea;
            public InternalState _internalState = InternalState.Inactive;


            public Racket Owner = null;
            

            /* 
             * time when this state starts to take place
             * in the state machine implementation here, it comes before the fading in process
             */
            public TimeSpan _initialTime = default(TimeSpan);

            public State _lastState = null;

            public bool _pendingTransition = false;

            public Texture2D storedTexture = null;

            public Rectangle ReachableArea
            {
                get { return _reachableArea; }
            }

            public State(Texture2D texture, Vector2 size, Rectangle regionInSource,
                Rectangle reachableArea)
                : base(texture, size, regionInSource)
            {
                _reachableArea = reachableArea;
            }

            public State(Texture2D texture, Vector2 size, Rectangle reachableArea)
                : base(texture, size)
            {
                _reachableArea = reachableArea;
            }

            public State(Texture2D texture, Rectangle reachableArea)
                : base(texture)
            {
                _reachableArea = reachableArea;
            }

            public virtual Rectangle Rectangle
            {
                get
                {
                    return base.RectInTarget;
                }
            }

            public TimeSpan InitialTime { get { return _initialTime; } }

            public InternalState InnerState 
            { 
                get { return _internalState; }
                
                set 
                {
                    _internalState = value;
                    _pendingTransition = IsFadingIn;
                }
            }

            public virtual void DirectDraw(SpriteBatch sBatch, Vector2 position, Color color)
            {
                base.Draw(sBatch, position, color);
            }

            public virtual void DirectDraw(SpriteBatch sBatch, Vector2 position, Color color, float angle)
            {
                base.Draw(sBatch, position, color, angle);
            }

            public bool PreDraw()
            {
                if (this.Owner != null && this.Owner.Shattered)
                {
                    if (base.Texture != this.Owner.ShatteredTexture)
                    {
                        storedTexture = base.Texture;
                    }
                    base.Texture = this.Owner.ShatteredTexture;
                }
                else if (storedTexture != null)
                {
                    base.Texture = storedTexture;
                }
                return !(this.Owner != null && this.Owner.finished);
            }

            public override void Draw(SpriteBatch sBatch, Vector2 position, Color color)
            {
                if (!PreDraw())
                    return;

                if (this.InnerState == InternalState.Starting
                    && _lastState != null && _lastState != this)
                {
                    // current racket-state is not ready to be drawn, 
                    // draw instead last-state which is available 
                    // make sure the previous racket-state which is not the same as the current
                    // one doesn't have an internal state of Starting.
                    // this situation can never happen
                    // but to be on the safe side, a separate method named 'DirectDraw' is 
                    // defined for drawing by invokiong the plain drawing method

                    _lastState.DirectDraw(sBatch, position, color);
                }
                else
                {
                    base.Draw(sBatch, position, color);
                }
            }

            
            public override void Draw(SpriteBatch sBatch, Vector2 position, Color color, float angle)
            {
                if (!PreDraw())
                    return;

                if (this.InnerState == InternalState.Starting 
                    && _lastState != null && _lastState != this)
                {
                    // current state is not ready, draw last-state which is available instead
                    // make sure that it is not in Staring state
                    _lastState.DirectDraw(sBatch, position, color, angle);                   
                }
                else
                {
                    base.Draw(sBatch, position, color, angle);
                }
            }


            /* 
             * <code comments = "methods that provide uniform behaviour among stateful rackets">
             */
            public virtual void Initiate(State lastState)
            {
                _lastState = lastState;
                if (_lastState != null)
                {
                    _pendingTransition = lastState.NeedsTransition;
                }
                if (_lastState != this)
                {
                    Debug.WriteLine(String.Format("staring from {0} to {1}", _lastState, this),
                        System.Diagnostics.TraceEventType.Information);

                    this._internalState = InternalState.Starting;
                    if (_lastState != null)
                    {
                        _lastState.InnerState = InternalState.Inactive;
                    }
                }
                this._internalState = InternalState.Starting;
            }

            /*
             * the behaviour of the method and all its overriding methods must satisfy:
             * 1. updating process is passed on to the subsequent user implemented portion
             *    of the 'update' method which calls this method, and that functional 
             *    code would act correspondingly based on the internal state
             * 2. turn the state to Inactive when life expires, this is required by the 
             *    standard behaviour of racket updating
             */
            public virtual void PreUpdate(Racket racket, GameTime gameTime)
            {
                if (this.InnerState == InternalState.Starting)
                {
                    Debug.WriteLine(String.Format("reset initial time for state {0}", this), 
                        System.Diagnostics.TraceEventType.Critical);

                    _initialTime = gameTime.TotalGameTime;

                    if (this.NeedsTransition)
                    {
                        this.InnerState = InternalState.FadeIn;
                        // and the former state is doing its fading out process
                        _lastState.FadeOut();
                    }
                    else
                    {   // move onto activate directly
                        // this may be a result of restaring the same state
                        this.InnerState = InternalState.Active;
                        if (_lastState != this)
                        {
                            _lastState.Inactivate();
                        }
                    }
                }

                if ((gameTime.TotalGameTime - _initialTime).CompareTo(this.LifeSpan) >= 0)
                {
                    /**
                     * from active to inactive
                     */
                    Inactivate();
                }
                else if (this.InnerState == InternalState.FadeIn
                    || this.InnerState == InternalState.FadingIn)
                {
                    FadingInUpdate(racket, gameTime, _lastState);
                    if (this.InnerState == InternalState.FadeIn)
                    {
                        this.InnerState = InternalState.FadingIn;
                    }
                }
            }

            /**
             * called when the racket state starts to be taken place of
             */
            public virtual void FadeOut()
            {
            }

            /**
             * called when the racket state is put to an end
             */
            public virtual void Inactivate()
            {
                this.InnerState = InternalState.Inactive;
            }

            /*
             * this method and all its overriding methods must duly turn the state to Active
             * may be called multiple times
             */
            public virtual void FadingInUpdate(Racket racket, GameTime gameTime, State fadingOutState)
            {
                this.InnerState = InternalState.Active;
                fadingOutState.Inactivate();
            }

            /* 
             * </code>
             */

            public virtual TimeSpan LifeSpan
            {
                get
                {
                    return TimeSpan.MaxValue;   /* by default it's set to the maximum */
                }
            }

            public virtual bool IsActive
            {
                get 
                { 
                    return _internalState == InternalState.Active 
                        || _internalState == InternalState.FadeIn 
                        || _internalState == InternalState.FadingIn; 
                }
            }

            public virtual bool IsFadingIn
            {
                get
                {
                    return _internalState == InternalState.FadeIn
                        || _internalState == InternalState.FadingIn; 
                }
            }

            public virtual bool HasDisappeared
            {
                get 
                { 
                    return _internalState == InternalState.Inactive; 
                }
            }

            /*
             * need of transition from last state to this
             * if this property returns false, no transition will be peformed
             * based on the default behaviour of 3, even if last
             * state has a fade-out associated with it.
             */
            public virtual bool NeedsTransition
            {
                get
                {
                    return _pendingTransition || _lastState != null && _lastState != this
                        && (_lastState.FadingOutDuration.CompareTo(TimeSpan.Zero) > 0
                        || this.FadingInDuration.CompareTo(TimeSpan.Zero) > 0);
                }
            }

            /*
             * recommended duration for fade-out process
             * not necessarily stuck to by transition performer
             */
            public virtual TimeSpan FadingOutDuration
            {
                get
                {
                    return TimeSpan.Zero;
                }
            }

            /*
             * recommended duration for fade-in process
             * not necessarily stuck to by transition performer
             */
            public virtual TimeSpan FadingInDuration
            {
                get
                {
                    /*
                     * designate some time for fade-in process
                     */
                    if (IsFadingIn)
                    {
                        return new TimeSpan(0, 0, 1);
                    }
                    return TimeSpan.Zero;
                }
            }


            public virtual bool IsFree
            {
                get
                {
                    return false;
                }
            }
        }

        public TimeSpan _lastGameTime = default(TimeSpan);

        bool _disposed = false;         // flag that indicates whether the object has been disposed

        public bool finished = false;

        public Vector2 Position;    // Position on screen
        public Rectangle _rectangle; // Rectangle which represents where the sprite picture is

        public Vector2 _lastPosition;
        public Vector2 _velocity;

        public Shots _shots; // Shots container is maintained by Racket

        public Dictionary<Type, State> StateCache = new Dictionary<Type,State>();


        public Vector2 _racketSize;
        public Rectangle _arena;

        // shatter
        TimeSpan _shatterInit = default(TimeSpan);
        TimeSpan _shatterDur = new TimeSpan(0, 0, 1);
        bool Shattered = false;
        Texture2D ShatteredTexture = null;


        public State CurrentState
        {
            get;
            set;
        }

        public Level Level
        {
            get;
            set;
        } 


        // required by IPhysicalObject
        public float Weight
        {
            get { throw new NotImplementedException(); }
        }


        public Rectangle ReachableArea
        {
            get { return CurrentState.ReachableArea; }
        }

        public Rectangle Rectangle
        {
            get
            {
                return CurrentState.Rectangle;
            }
        }

        public Racket(Game game, Rectangle arena, Bricks bricks)
            : base(game)
        {
            _racketSize = new Vector2(bricks.BrickSize.X * 1.5f, bricks.BrickSize.Y);
            _arena = arena;

            _shots= new Shots(game, arena, bricks);

            /**
             * initially only populate the cache with normal state and 
             * set current state to the normal racket state
             */
            StateCache[typeof(NormalState)]  =
                CurrentState = new NormalState(game, _racketSize, arena);
            StateCache[typeof(NormalState)].Owner = this;

            Position = new Vector2();
            
            // TODO: Construct any child components here
        }

        /// <summary>
        /// Allows the _game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here
            if (_shots != null)
            {
                _shots.AddToLayer(((MainGame)this.Game).ShotsLayer);
            }

            base.Initialize();
        }

        /// <summary>
        /// Allows the _game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            if (Shattered)
            {
                if (_shatterInit == default(TimeSpan))
                {
                    _shatterInit = gameTime.TotalGameTime;
                }
                if (gameTime.TotalGameTime - _shatterInit >= _shatterDur)
                {
                    Shattered = false;

                    if (!Level.Stopped && Level.Lives > 0)
                    {
                        Level.Lives--;
                        this.PutInStartPosition();
                        if (Level.Ball != null)
                        {
                            Level.Ball.RemoveFromGame();
                        }

                        Level.Ball = new Ball(Game, this._arena, Level.Bricks, this);
                        Level.Ball.PutInStartPosition();
                        Level.Ball.AddToLayer(((MainGame)Game).BallLayer);
                    }
                    else if (!finished)
                    {
                        Level.Finish();
                        ((MainGame)Game).GameOver();
                        finished = true;
                    }
                }
                return;
            }

            if (Level.Stopped)
            {
                return;
            }

            CurrentState.Update(this, gameTime);

            if (!CurrentState.IsActive && CurrentState.GetType() != typeof(NormalState))
            {
                TurnToState(typeof(NormalState));
            }

            if (_lastGameTime != default(TimeSpan))
            {
                TimeSpan timeDiff = gameTime.TotalGameTime - _lastGameTime;
                Vector2 posDiff = Position - _lastPosition;
                _velocity = posDiff / timeDiff.Milliseconds;
            }

            if (Level.Ball.IsDead)
            {
                Shatter();
            }

            if (Level.Ball != null && Level.Ball._lifeState == Ball.LifeState.Activating)
            {
                Level.Ball.FollowRacket();
            }

            _lastGameTime = gameTime.TotalGameTime;
            _lastPosition = Position;

            base.Update(gameTime);
        }

        public void Shatter()
        {
            if (ShatteredTexture == null)
            {
                ShatteredTexture = Game.Content.Load<Texture2D>("racket_destroyed");
            }
            ((MainGame)Game).SoundResources.PlayShatter();
            Shattered = true;
            _shatterInit = default(TimeSpan);
        }

        public void HasteToNormal()
        {
            CurrentState = StateCache[typeof(NormalState)];
            CurrentState.Initiate(CurrentState);
        }
        
        public override void Draw(GameTime gameTime)
        {
            // Get the current sprite batch
            SpriteBatch sBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            CurrentState.Draw(sBatch, Position, Color.White);

            base.Draw(gameTime);
        }

        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // Cleanup managed objects
                }
                // Cleanup unmanaged objects if any
                // ...
            }

            _disposed = true;
        }

        public void Collides(IPhysicalObject phyObj)
        {
            /*
             * Warning: Keep the algorithm simple!
             */

            if (phyObj is Ball)
            {
                Ball ball = (Ball)phyObj;

                int top = (int)Math.Floor(this.Position.Y - CurrentState.HalfSize.Y);
                int bottom = (int)Math.Ceiling(this.Position.Y + CurrentState.HalfSize.Y);
                int left = (int)Math.Floor(this.Position.X - CurrentState.HalfSize.X);
                int right = (int)Math.Ceiling(this.Position.X + CurrentState.HalfSize.X);

                Vector2 hitPoint;
                Utility.HitBorder hitBorder;

                bool collided = Utility.Collide(ball.Position, ball.Radius, 
                    new Rectangle(left, top, right - left, bottom - top),
                    out hitPoint, out hitBorder);

                switch (hitBorder)
                {
                    case Utility.HitBorder.Top:
                        if (ball.Velocity.Y > _velocity.Y)
                        {
                            Random r = new Random();
                            float vx = ball.Velocity.X;
                            float vy = ball.Velocity.Y;
                            float v = (float)Math.Sqrt(vx * vx + vy * vy);
                            float dx = hitPoint.X - this.Position.X;
                            float halfWidth = CurrentState.HalfSize.X;
                            float xrate = dx / halfWidth;
                            xrate = (float)Math.Min(Math.Max(xrate, -0.99), 0.99);
                            float angle = xrate * (float)Math.PI * 0.4f;

                            vx = v * (float)Math.Sin(angle);
                            vy = -v * (float)Math.Cos(angle);

                            // ball driven by the racket
                            //vx += this._velocity.X;
                            //vy += this._velocity.Y;

                            ball.Velocity = new Vector2(vx, vy);

                            // play sound
                            ((MainGame)Game).SoundResources.PlayHit();
                        }
                        break;
                    case Utility.HitBorder.Bottom:
                        // not implemented yet, do nothing for the time being
                        break;
                }
            }
        }

        public float BaseY
        {
            get
            {
                return ReachableArea.Bottom - this.CurrentState.Size.Y * 0.5f;
            }
        }

        public float BaseX
        {
            get
            {
                return ReachableArea.Left + ReachableArea.Width * 0.5f;
            }
        }

        public void PutInStartPosition()
        {
            Position.X = this.BaseX;
            Position.Y = this.BaseY;

            finished = false;

            HasteToNormal();

            //TurnToState(typeof(NormalState));
        }

        public void TurnToState(Type stateType)
        {
            State lastState = CurrentState;

            if (!StateCache.ContainsKey(stateType))
            {
                System.Reflection.ConstructorInfo ci
                    = stateType.GetConstructor(new Type[]{typeof(Game), typeof(Vector2), typeof(Rectangle)});
                if (ci == null)
                {
                    System.Reflection.ConstructorInfo[] cis = stateType.GetConstructors();
                    foreach (System.Reflection.ConstructorInfo ici in cis)
                    {
                        System.Reflection.ParameterInfo[] parms = ici.GetParameters();
                        if (parms[0].ParameterType == typeof(Game)
                            && parms[1].ParameterType == typeof(Vector2)
                            && parms[2].ParameterType == typeof(Rectangle))
                        {
                            if (parms.Length == 4 && parms[3].ParameterType == typeof(Shots))
                            {
                                /* load shots */
                                ci = ici;
                                StateCache[stateType] = (State)ci.Invoke(new object[] { 
                                    this.Game, this._racketSize, this._arena, this._shots });
                                StateCache[stateType].Owner = this;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    StateCache[stateType] = (State)ci.Invoke(new object[] { this.Game, this._racketSize, this._arena });
                    StateCache[stateType].Owner = this;
                }

                if (ci == null)
                {
                    // cancel the change
                    return;
                }
            }

            ((MainGame)Game).SoundResources.PlayMorph();

            CurrentState = StateCache[stateType];
            CurrentState.Initiate(lastState);

            Debug.WriteLine(String.Format("switch from {0} to {1}", 
                lastState.GetType(), CurrentState.GetType()), 
                System.Diagnostics.TraceEventType.Information);
        }
    }
}